<?php
/**
 * Created by PhpStorm.
 * User: 85210755@qq.com
 * NickName: 柏宇娜
 * Date: 2018/2/12 9:29
 */

namespace Payment;

use Payment\Wx\lib\WxPayApi;
use Payment\Wx\lib\JsApiPay;
use Payment\Wx\lib\NativePay;
use Payment\Wx\lib\WxPayRefund;
use Payment\Wx\lib\WxPayOrderQuery;
use Payment\Wx\lib\WxPayRefundQuery;
use Payment\Wx\lib\WxPayUnifiedOrder;
use Payment\Wx\lib\WxPayException;
use Payment\Wx\lib\WxPayResults;

class WxPay
{
    private $config = [
        'appid'           => '',
        'mchid'           => '',
        'key'             => '',
        'secret'          => '',
        'sslcert_path'    => '',
        'sslkey_path'     => '',
        'curl_proxy_host' => '0.0.0.0',
        'curl_proxy_port' => 0,
        'report_level'    => 1,
        'notify_url'      => ''
    ];

    public function __construct(array $config = [])
    {
        $this->config = array_merge($this->config, $config);
    }

    public function app($data)
    {
        $input = new WxPayUnifiedOrder($this->config['key']);
        $input->SetBody($data['subject']);
        $input->SetOut_trade_no($data['out_trade_no']);
        $input->SetTotal_fee($data['total_fee'] * 100);
        $input->SetSpbill_create_ip($this->get_client_ip());
        $input->SetNotify_url($data['notify_url']);
        $input->SetTrade_type("APP");
        $pay    = new WxPayApi($this->config);
        $result = $pay->unifiedOrder($input);
        if ($result['return_code'] == 'FAIL') {
            return ['ret' => 2, 'msg' => $result['return_msg']];
        }
        if ($result['result_code'] == 'FAIL') {
            return ['ret' => 2, 'msg' => $result['err_code_des']];
        }
        return $result;
    }

    public function jsPay($data)
    {
        $tools  = new JsApiPay($this->config);
        $openId = $tools->GetOpenid();
        $input  = new WxPayUnifiedOrder($this->config['key']);
        $input->SetBody($data['subject']);
        $input->SetOut_trade_no($data['out_trade_no']);
        $input->SetTotal_fee($data['total_fee'] * 100);
        $input->SetNotify_url($data['notify_url']);
        $input->SetTrade_type("JSAPI");
        if ($data['goods_tag']) $input->SetGoods_tag($data['goods_tag']);
        if ($data['attach']) $input->SetAttach($data['attach']);

        $input->SetOpenid($openId);
        $pay             = new WxPayApi($this->config);
        $order           = $pay->unifiedOrder($input);
        $jsApiParameters = $tools->GetJsApiParameters($order);
        return $jsApiParameters;
    }

    public function h5Pay_bak($data)
    {
        $input = new WxPayUnifiedOrder($this->config['key']);
        $input->SetBody($data['subject']);
        $input->SetOut_trade_no($data['out_trade_no']);
        $input->SetTotal_fee($data['total_fee'] * 100);
        $input->SetNotify_url($data['notify_url']);
        $input->SetTrade_type("MWEB");
        if ($data['goods_tag']) $input->SetGoods_tag($data['goods_tag']);
        if ($data['attach']) $input->SetAttach($data['attach']);
        $pay    = new WxPayApi($this->config);
        $result = $pay->unifiedOrder($input);
        if ($result['return_code'] == 'FAIL') {
            return ['ret' => 1, 'msg' => $result['return_msg']];
        }
        if ($result['result_code'] == 'FAIL') {
            return ['ret' => 1, 'msg' => $result['err_code_des']];
        }
        return ['ret' => 0, 'data' => $result['mweb_url'] . '&redirect_url=' . urlencode($data['return_url'])];
    }

    public function h5Pay($payData)
    {
        $input = new WxPayUnifiedOrder($this->config['key']);
        $input->SetBody($payData['subject']);
        $input->SetOut_trade_no($payData['out_trade_no']);
        $input->SetTotal_fee($payData['total_fee'] * 100);
        $input->SetNotify_url($payData['notify_url']);
        $input->SetTrade_type("MWEB");
        if ($payData['goods_tag']) $input->SetGoods_tag($payData['goods_tag']);
        if ($payData['attach']) $input->SetAttach($payData['attach']);
        $pay    = new WxPayApi($this->config);
        $result = $pay->unifiedOrder($input);
        if ($result['return_code'] == 'FAIL') {
            return ['ret' => 1, 'msg' => $result['return_msg']];
        }
        if ($result['result_code'] == 'FAIL') {
            return ['ret' => 1, 'msg' => $result['err_code_des']];
        }
        return ['ret' => 0, 'data' => $result['mweb_url'] . '&redirect_url=' . urlencode($payData['return_url'])];
    }

    public function qrcodePay($payData)
    {
        //$subject, $out_trade_no, $total_fee, $notify_url, $build_png = false, $goods_tag = '', $attach = ''
        $input = new WxPayUnifiedOrder($this->config['key']);
        $input->SetBody($payData['subject']);
        $input->SetOut_trade_no($payData['out_trade_no']);
        $input->SetTotal_fee($payData['total_fee'] * 100);
        $input->SetNotify_url($payData['notify_url']);
        $input->SetTrade_type("NATIVE");
        if (isset($payData['product_id'])) $input->SetProduct_id($payData['product_id']);
        if (isset($payData['goods_tag'])) $input->SetGoods_tag($payData['goods_tag']);
        if (isset($payData['attach'])) $input->SetAttach($payData['attach']);

        $tool   = new NativePay();
        $result = $tool->GetPayUrl($input, $this->config);

        if ($result['return_code'] == 'FAIL') {
            return ['ret' => 2, 'msg' => $result['return_msg']];
        }
        if ($result['result_code'] == 'FAIL') {
            return ['ret' => 2, 'msg' => $result['err_code_des']];
        }

        return ['ret' => 0, 'data' => "http://qr.zy52.cn/?data={$result['code_url']}"];
    }

    /**
     * @param string $out_trade_no
     * @param string $trade_no 商户订单号、微信交易号 参数二选一
     * @return array
     */
    public function query($out_trade_no = '', $trade_no = '')
    {
        $input = new WxPayOrderQuery($this->config['key']);
        if ($out_trade_no) {
            $input->SetOut_trade_no($out_trade_no);
        } elseif ($trade_no) {
            $input->SetTransaction_id($trade_no);
        } else {
            return ['ret' => 0, 'msg' => '订单号与微信交易号不能同时为空'];
        }
        $tool   = new WxPayApi($this->config);
        $result = $tool->orderQuery($input);

        if ($result['return_code'] != 'SUCCESS') {
            return ['ret' => 2, 'msg' => $result['return_msg']];
        }
        if ($result['result_code'] != 'SUCCESS') {
            return ['ret' => 2, 'msg' => $result['err_code_des']];
        }
        $trade_des = [
            'SUCCESS'    => '支付成功',
            'REFUND'     => '转入退款',
            'NOTPAY'     => '未支付',
            'CLOSED'     => '已关闭',
            'REVOKED'    => '已撤销（付款码支付）',
            'USERPAYING' => '用户支付中（付款码支付）',
            'PAYERROR'   => '支付失败(其他原因，如银行返回失败'
        ];
        if ($result['trade_state'] != 'SUCCESS') {
            return ['ret' => 2, 'msg' => $trade_des[$result['trade_state']] ?? '支付失败,未知错误'];
        }
        return [
            'ret'  => 0,
            'data' => [
                'attach'           => $result['attach']??'',//支付时提交的附加数据
                'bank_type'        => $result['bank_type'],//银行类型
                'cash_fee'         => $result['cash_fee'] / 100,//支付金额
                'fee_type'         => $result['fee_type']??'未知币种',//支付币种
                'is_subscribe'     => $result['is_subscribe'],//是否关注公众号 Y
                'openid'           => $result['openid'],//
                'out_trade_no'     => $result['out_trade_no'],//商户订单号
                'time_end'         => $result['time_end'],//支付完成时间戳
                'total_fee'        => $result['total_fee'] / 100,//订单总金额
                'trade_state'      => $result['trade_state'],//交易状态
                'trade_state_desc' => $result['trade_state_desc'],//交易状态描述
                'trade_type'       => $result['trade_type'],//交易类型
                'transaction_id'   => $result['transaction_id'],//交易类型
            ]];
    }

    /**
     * @param $data array 同一订单支付多次退款，每次退款需更换不同的退款单号
     * @return array    可分多次退款，最多50次；
     */
    public function refund($data)
    {
        $input = new WxPayRefund($this->config['key']);
        if ($data['out_trade_no']) {
            $input->SetOut_trade_no($data['out_trade_no']);
        } elseif ($data['trade_no']) {
            $input->SetTransaction_id($data['trade_no']);
        } else {
            return ['ret' => 1, 'msg' => '订单号与微信交易号不能同时为空'];
        }

        $input->SetTotal_fee($data['total_fee'] * 100);
        $input->SetRefund_fee($data['refund_fee'] * 100);
        $input->SetOut_refund_no($data['refund_no']);
        $input->SetOp_user_id($this->config['mchid']);
        $tool   = new WxPayApi($this->config);
        $result = $tool->refund($input);

        if ($result['return_code'] == 'FAIL') {
            return ['ret' => 2, 'msg' => $result['return_msg']];
        }
        if ($result['result_code'] == 'FAIL') {
            return ['ret' => 2, 'msg' => $result['err_code_des']];
        }
        return ['ret' => 0, 'data' => [
            'out_trade_no'  => $result['out_trade_no'],        //商户订单号码
            'out_refund_no' => $result['out_refund_no'],       //商户提交的退款单号
            'refund_id'     => $result['refund_id'],           //微信退款单号
            'total_fee'     => $result['total_fee'] / 100,     //订单总金额
            'refund_fee'    => $result['refund_fee'] / 100,    //退款金额
        ]];
    }

    /**
     * @param array $data
     * @return array
     */
    public function refundQuery($data)
    {
        //$out_trade_no = '', $trade_no = '', $out_refund_no = '', $refund_no = ''
        $input = new WxPayRefundQuery($this->config['key']);
        if ($data['out_trade_no']) {
            $input->SetOut_trade_no($data['out_trade_no']);
        } elseif ($data['trade_no']) {
            $input->SetTransaction_id($data['trade_no']);
        } elseif ($data['out_refund_no']) {
            $input->SetOut_refund_no($data['out_refund_no']);
        } elseif ($data['refund_no']) {
            $input->SetRefund_id($data['refund_no']);
        } else {
            return ['ret' => 1, 'msg' => '订单号与微信交易号不能同时为空'];
        }

        $tool   = new WxPayApi($this->config);
        $result = $tool->refundQuery($input);
        if ($result['return_code'] == 'FAIL')
            return ['ret' => 2, 'msg' => $result['return_msg']];
        if ($result['result_code'] == 'FAIL')
            return ['ret' => 2, 'msg' => $result['err_code_des']];

        unset(
            $result['appid'],
            $result['mch_id'],
            $result['nonce_str'],
            $result['result_code'],
            $result['return_code'],
            $result['return_msg'],
            $result['sign']
        );
        for ($i = 0; $i < 50; $i++) {
            if (array_key_exists('refund_id_' . $i, $result)) {
                $result['refund_fee_' . $i] /= 100;//单次退款金额
            } else {
                break;
            }
        }
        $result['cash_fee']   /= 100;//订单支付总金额
        $result['refund_fee'] /= 100;//累计退款金额
        return ['ret' => 0, 'data' => $result];
    }

    /**
     * @param $key
     * @return array|bool notify OK后返回验证数组
     */
    public function notify($func)
    {
        $xml = file_get_contents('php://input');
        //如果返回成功则验证签名
        try {
            $tool   = new WxPayResults($this->config['key']);
            $result = $tool->Init($xml, $this->config['key']);
        } catch (WxPayException $e) {
            exit($e->errorMessage());
        }
        if ($result['return_code'] == 'SUCCESS' && $result['result_code'] == 'SUCCESS') {
            $param = [
                'out_trade_no'   => $result['out_trade_no'],
                'transaction_id' => $result['transaction_id'],
                'total_amount'   => $result['total_fee'] / 100,
                'pay_code'       => strtolower(__CLASS__),
                'pay_name'       => '微信支付',
            ];
            $res   = call_user_func($func, $param);
            if ($res === true) {
                $tool->SetData('return_code', 'SUCCESS');
                $tool->SetData('return_msg', 'OK');
                $xml = $tool->ToXml();
                exit($xml);
            }
        }

        exit('FAIL');
    }

    private function get_client_ip()
    {
        $cip = 'unknown';
        if ($_SERVER['REMOTE_ADDR'])
            $cip = $_SERVER['REMOTE_ADDR'];
        elseif (getenv('REMOTE_ADDR'))
            $cip = getenv('REMOTE_ADDR');
        return $cip;
    }
}